
/* 
 * Linux networking class for CSE 360 Project server application
 * written by Filip Jaros
 * fjaros@asu.edu
 */

#include "network.h"

/**
 * constructor for network class.
 * initializes all necessary variables.
 */
network::network()
{
    // Initialize class variables
    net.socks = NULL;
    FD_ZERO(&net.ms);
    net.maxfd = -1;
    pos = 0;
    nextlen = 0;
}

/**
 * destructor for network class.
 * place holder but in theory should never be called.
 */
network::~network()
{

}

/**
 * Start() initializes the network to listen either on the first found local IP or an
 * IP specified in the ip parameter.
 * @param ip
 * @param port
 * @return
 */
int network::Start(char *ip, unsigned short port)
{
    net.port = port;

    if (ip)
        strcpy(net.addr, ip);
    else
    {
        cout << "Attempting to retrieve local IP..." << endl;
        if (!GetLocalIP())
        {
            cout << "Unable to find local IP address" << endl;
            return 0;
        }
    }
    cout << "Networking listening on " << net.addr << ":" << port << endl;
    if ((net.listener = Listen()))
        Recv();
    return 0;
}

/**
 * Finds the first local IP address of the current computer and saves it in the
 * socket_holder struct.
 * @return true on success, false on failure.
 */
bool network::GetLocalIP()
{
    ifaddrs *ia;
    getifaddrs(&ia);

    while (ia)
    {
        if (ia->ifa_addr->sa_family == AF_INET)
        {
            void *tmp = &((sockaddr_in*)ia->ifa_addr)->sin_addr;
            inet_ntop(AF_INET, tmp, net.addr, INET_ADDRSTRLEN);
            if (strcmp(ia->ifa_name, "lo"))
                return true;
        }
        ia = ia->ifa_next;
    }
    memset(net.addr, '\0', sizeof(net.addr));
    strcpy(net.addr, "127.0.0.1");
    return true;
}

/**
 * Listen() starts a listening socket for the server
 * @return
 */
int network::Listen()
{
    int on = 1;

    // Setting up the socket for listening

    int listener = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (listener < 0)
    {
        cout << "socket() failed - " << strerror(errno) << endl;
        return 0;
    }

    // Make address reusable, in case we close/open the port too many times
    
    if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(int)))
    {
        cout << "setsockopt() failed - " << strerror(errno) << endl;
        return 0;
    }

    // Make socket non blocking i/o

    if (ioctl(listener, FIONBIO, (char*)&on) == -1)
    {
        cout << "ioctl() failed - " << strerror(errno) << endl;
        return 0;
    }

    // Create the socket structure to bind to

    sockaddr_in sa;
    memset(&sa, '\0', sizeof(sockaddr_in));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(net.port);
    sa.sin_addr.s_addr = inet_addr(net.addr);

    // Bind to this socket struct

    if (bind(listener, (sockaddr*)&sa, sizeof(sockaddr)))
    {
        cout << "bind() failed - " << strerror(errno) << endl;
        return 0;
    }

    // Listen

    if (listen(listener, SOMAXCONN))
    {
        cout << "listen() failed - " << strerror(errno) << endl;
        return 0;
    }
    
    FD_SET(listener, &net.ms);
    if (listener > net.maxfd)
        net.maxfd = listener;

    return listener;
}

/**
 * Close() closes the specicfic socket based on the input parameter and patches
 * the linked lists of active sockets.
 * @param list
 * @return
 */
network::LList_socks_t *network::Close(LList_socks_t *list)
{
    // Function for closing connection and removing socket from link.
    if (list)
    {
        if (list->sock)
        {
            close(list->sock);
            FD_CLR(list->sock, &net.ms);
        }
        if (net.maxfd == list->sock)
        {
            while (FD_ISSET(net.maxfd, &net.ms) == 0)
                net.maxfd--;
        }
        if (list->prev)
            list->prev->next = list->next;
        else
            net.socks = list->next;
        if (list->next)
            list->next->prev = list->prev;

        LList_socks_t *temp = list->next;
        delete list;
        list = NULL;
        return temp;
    }
    return NULL;
}

/**
 * Accept() accepts an incoming connection and creates a new socket for it.
 * @return
 */
int network::Accept()
{
    // Function for accepting connection requests.
    int on = 1;
    
    int newsock = accept(net.listener, NULL, NULL);
    if (newsock >= 0)
    {
        if (ioctl(newsock, FIONBIO, (char*)&on) != -1)
        {
            FD_SET(newsock, &net.ms);
            if (newsock > net.maxfd)
                net.maxfd = newsock;

            LList_socks_t *ns = new LList_socks_t;
            memset(ns, '\0', sizeof(LList_socks_t));
            if (net.socks)
            {
                net.socks->prev = ns;
                ns->next = net.socks;
            }
            net.socks = ns;
            net.socks->sock = newsock;
            net.socks->permissions = 0;
            memset(net.socks->username, '\0', sizeof(net.socks->username));

            close(net.listener);
            FD_CLR(net.listener, &net.ms);
            net.listener = Listen();
            return 1;
        }
        else
        {
            cout << "in Accept ioctl() failed - " << strerror(errno) << endl;
            close(newsock);
        }
    }
    else
    {
        if (errno != EWOULDBLOCK)
            cout << "accept() failed - " << strerror(errno) << endl;
    }
    return 0;
}

/**
 * Recv() waits on data to be received from the network and then forwards it to parser
 * for further parsing
 * @return 0 on failure of accept
 */
int network::Recv()
{
    // Function for receiving connections
    fd_set fs;

    for (;;)
    {
        memcpy(&fs, &net.ms, sizeof(fd_set));
        timeval tv;
        tv.tv_sec = 1;
        tv.tv_usec = 0;
        int s = select(net.maxfd + 1, &fs, NULL, NULL, &tv);
        if (s > 0)
        {
            if (FD_ISSET(net.listener, &fs))
            {
                if (!Accept())
                    return 0;
            }

            LList_socks_t *list = net.socks;
            while (list)
            {
                if (FD_ISSET(list->sock, &fs))
                {
                    char buf[1024];
                    int recvlen;
                    for (;;)
                    {
                        recvlen = recv(list->sock, buf, sizeof(buf), 0);
                        if (recvlen > 0)
                        {
                            if (!Parse(list->sock, list, buf, recvlen))
                            {
                                cout << "Parse() failed - Invalid data" << endl;
                                Close(list);
                                break;
                            }
                        }
                        else if (!recvlen)
                        {
                            cout << "recv() failed - Client closed connection" << endl;
                            Close(list);
                            break;
                        }
                        else
                        {
                            if (errno != EWOULDBLOCK)
                            {
                                cout << "recv() failed - " << strerror(errno) << endl;
                                Close(list);
                            }
                            break;
                        }
                    }
                }
                list = list->next;
            }
        }
    }
}

/**
 * defined operators pass different data values into the output streams
 * @param data
 * @return
 */
network &network::operator << (char *data)
{
    if (data)
    {
        int plen;
        if (nextlen)
        {
            plen = nextlen;
            nextlen = 0;
        }
        else
            plen = strlen(data) + 1;
        memcpy(sendbuf + pos, data, plen);
        pos += plen;
    }
    else
    {
        nextlen = 0;
        *this << (unsigned char)0x00;
    }
    return *this;
}

network &network::operator >> (int len)
{
    nextlen = len;
    return *this;
}

network &network::operator << (unsigned char data)
{
    *(unsigned char*)(sendbuf + pos) = data;
    pos += sizeof(unsigned char);
    return *this;
}

network &network::operator << (unsigned short data)
{
    *(unsigned short*)(sendbuf + pos) = data;
    pos += sizeof(unsigned short);
    return *this;
}

network &network::operator << (unsigned int data)
{
    *(unsigned int*)(sendbuf + pos) = data;
    pos += sizeof(unsigned int);
    return *this;
}
/**
 * Send() sends the data in the network output stream across a socket
 * @param socket
 * @param packetid
 * @return
 */
bool network::Send(int socket, unsigned char packetid)
{
    char buf[65536];
    memcpy(buf + 6, sendbuf, pos);
    buf[0] = 0xFF;
    buf[1] = packetid;
    pos += 6;
    *(unsigned int*)(buf + 2) = pos;
    int s = send(socket, buf, pos, 0);
    pos = 0;
    return (s != -1);
}

